package cn.felord.core.config;

import cn.felord.core.bcrypt.BCryptPasswordEncoder;
import cn.felord.core.bcrypt.PasswordEncoder;
import cn.felord.core.enumeration.Enumeratable;
import cn.felord.core.shiro.MyRealm;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.remoting.SecureRemoteInvocationExecutor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;

import java.util.HashMap;
import java.util.Map;

/**
 * shiro 配置
 *
 * @author Dax
 * @since 16 :25  2018/4/30
 */
@Configuration
public class ShiroConfig {


    /**
     * Security manager security manager.
     *
     * @param realm          the realm
     * @param ehcacheManager the ehcache manager
     * @return the security manager
     */
    @Bean
    public SecurityManager securityManager(Realm realm, CacheManager ehcacheManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        securityManager.setCacheManager(ehcacheManager);
        return securityManager;
    }

    /**
     * B crypt password encoder password encoder.
     *
     * @return the password encoder
     */
    @Bean
    public PasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder("AAAAA", 10);
    }

    /**
     * B crypt credentials matcher credentials matcher.
     *
     * @param passwordEncoder the password encoder
     * @return the credentials matcher
     */
    @Bean
    @ConditionalOnBean(PasswordEncoder.class)
    public CredentialsMatcher bCryptCredentialsMatcher(PasswordEncoder passwordEncoder) {
        return (token, info) -> {
            Object tokenCredentials = token.getCredentials();

            String rawPassword = new String((char[]) tokenCredentials);
            Object infoCredentials = info.getCredentials();
            String encodedPassword = (String) infoCredentials;
            return passwordEncoder.matches(rawPassword, encodedPassword);
        };
    }

    /**
     * Ehchache manager cache manager.
     *
     * @return the cache manager
     */
    @Bean
    public CacheManager ehcacheManager() {
        EhCacheManager cacheManager = new EhCacheManager();
        cacheManager.setCacheManagerConfigFile("classpath:shiro-ehcache.xml");
        return cacheManager;
    }

    /**
     * Realm realm.
     *
     * @param credentialsMatcher the credentials matcher
     * @return the realm
     */
    @Bean
    @ConditionalOnBean(CredentialsMatcher.class)
    public Realm realm(CredentialsMatcher credentialsMatcher) {
        MyRealm realm = new MyRealm();
        realm.setCredentialsMatcher(credentialsMatcher);
        return realm;
    }

    /**
     * Authorization attribute source advisor authorization attribute source advisor.
     *
     * @param webSecurityManager the web security manager
     * @return the authorization attribute source advisor
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager webSecurityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(webSecurityManager);
        return authorizationAttributeSourceAdvisor;
    }


    /**
     * Secure remote invocation executor secure remote invocation executor.
     *
     * @param webSecurityManager the web security manager
     * @return the secure remote invocation executor
     */
    @Bean
    public SecureRemoteInvocationExecutor secureRemoteInvocationExecutor(SecurityManager webSecurityManager) {
        SecureRemoteInvocationExecutor secureRemoteInvocationExecutor = new SecureRemoteInvocationExecutor();
        secureRemoteInvocationExecutor.setSecurityManager(webSecurityManager);
        return secureRemoteInvocationExecutor;
    }

    /**
     * Delegating filter proxy filter registration bean.
     *
     * @return the filter registration bean
     */
    @Bean
    public FilterRegistrationBean delegatingFilterProxy() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        DelegatingFilterProxy proxy = new DelegatingFilterProxy();
        proxy.setTargetFilterLifecycle(true);
        proxy.setTargetBeanName("shiroFilterFactoryBean");
        filterRegistrationBean.setFilter(proxy);
        return filterRegistrationBean;
    }

    /**
     * Shiro filter factory bean shiro filter factory bean.
     *
     * @param webSecurityManager the web security manager
     * @return the shiro filter factory bean
     */
    @Bean("shiroFilterFactoryBean")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager webSecurityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(webSecurityManager);
        Map<String, String> filterChainDefinitionMap = new HashMap<>();
        filterChainDefinitionMap.put("/favicon.ico", AuthEnum.ANON.value);
        filterChainDefinitionMap.put("/app/**", AuthEnum.ANON.value);
        filterChainDefinitionMap.put("/plugins/**", AuthEnum.ANON.value);
        filterChainDefinitionMap.put("/images/**", AuthEnum.ANON.value);
        filterChainDefinitionMap.put("/login/captcha", AuthEnum.ANON.value);
        filterChainDefinitionMap.put("/login/doLogin", AuthEnum.ANON.value);
        filterChainDefinitionMap.put("/login", AuthEnum.ANON.value);
        filterChainDefinitionMap.put("/logout", AuthEnum.LOGOUT.value);
        filterChainDefinitionMap.put("/error/**", AuthEnum.ANON.value);
        filterChainDefinitionMap.put("/**", AuthEnum.AUTHC.value);
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setSuccessUrl("/");
        shiroFilterFactoryBean.setUnauthorizedUrl("/error/illegalAccess");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;

    }

    /**
     * The enum Auth enum.
     */
    enum AuthEnum implements Enumeratable {
        /**
         * The Anon.
         */
        ANON("anon", "no auth"),
        /**
         * The Logout.
         */
        LOGOUT("logout", "logout url"),
        /**
         * Authc auth enum.
         */
        AUTHC("authc", "authc  access");

        private String value;
        private String description;

        AuthEnum(String value, String description) {
            this.value = value;
            this.description = description;
        }

        public String value() {
            return this.value;
        }

        public String description() {
            return this.description;
        }
    }
}
